package jdesktop.drag;

import java.awt.*;
import java.awt.event.ComponentListener;
import java.awt.event.ComponentEvent;

public class ConnectedComponent implements ComponentListener
{
    private Axis     axis;
    private Component master;
    private Component slave;

    /**
     * -1.0, places before master,
     * -1.0->1.0 places so that it intersects the component. 0.0 - the master and slave are
     * centered.
     * 1.0 - the slave placed after the component.
     */
    private double     placement;

    public enum Axis
    {
        X,
        Y
    }

    public ConnectedComponent(Axis axis, Component master, Component slave)
    {
        this.axis = axis;
        this.master = master;
        this.slave = slave;
    }

    public double getPlacement()
    {
        return placement;
    }

    public void setPlacement(double placement)
    {
        this.placement = placement;
        positionSlave();
    }

    public void componentResized(ComponentEvent e)
    {
        positionSlave();
    }

    public void componentMoved(ComponentEvent e)
    {
        positionSlave();
    }

    private void positionSlave()
    {
        Rectangle newBounds = computeSlavePosition(placement);
        slave.setBounds(newBounds);
    }

    public Rectangle computeSlavePosition(double placement)
    {
        Rectangle s = slave.getBounds();
        Rectangle m = master.getBounds();
        return computeSlavePosition(s, m, placement);
    }



    protected Rectangle computeSlavePosition(Rectangle slaveBounds, Rectangle masterBounds, double placement)
    {
        // constrain location/width in opposite direction
        setLocation(H(), slaveBounds, getLocation(H(), masterBounds));
        setSize(H(), slaveBounds, getSize(H(), masterBounds));

        // height is unconstrained, location is set to align with the master
        int pos = computeSlaveLocation(
                getLocation(V(), masterBounds),
                getSize(V(), masterBounds),
                getSize(V(), slaveBounds),
                placement);
        setLocation(V(), slaveBounds, pos);
        return slaveBounds;
    }

    private int computeSlaveLocation(int masterLocation, int masterSize, int slaveSize)
    {
        return computeSlaveLocation(masterLocation, masterSize, slaveSize, placement);
    }

    static int computeSlaveLocation(int masterLocation, int masterSize, int slaveSize, double placement)
    {
        int pos;
        if (placement<-1.0)
        {
            int offset = round(placement*slaveSize);
            pos = masterLocation + offset;
        }
        else if (placement <= 1.0)
        {
            int range = masterSize+slaveSize;
            // get range [-1.0, 1.0] into range [0.0,1.0]
            double factor = (placement+1.0)/2.0;
            int offset = round(range*factor);
            assert(offset >= 0);
            assert(offset <= range);
            pos = masterLocation-slaveSize+offset;
        }
        else
        {
            assert(placement>=1.0);
            int offset = round((placement-1.0)*slaveSize);
            pos = masterLocation + masterSize + offset;
        }
        return pos;
    }

    private static int round(double v)
    {
        return (int) v;
    }

    void setLocation(Axis axis, Rectangle r, int value)
    {
        if (axis==Axis.X)
            r.x = value;
        else
            r.y = value;
    }

    int getLocation(Axis axis, Rectangle r)
    {
        if (axis==Axis.X)
            return r.x;
        else
            return r.y;
    }

    void setSize(Axis axis, Rectangle r, int value)
    {
        if (axis==Axis.X)
            r.width = value;
        else
            r.height = value;
    }

    int getSize(Axis axis, Rectangle r)
    {
        if (axis==Axis.X)
            return r.width;
        else
            return r.height;
    }


    private Axis H()
    {
        return opposite(axis);
    }

    private Axis opposite(Axis axis)
    {
        return axis==Axis.X ? Axis.Y : Axis.X;
    }

    private Axis V()
    {
        return axis;
    }

    public void componentShown(ComponentEvent e)
    {
        slave.setVisible(true);
    }

    public void componentHidden(ComponentEvent e)
    {
        slave.setVisible(false);
    }

    public static ConnectedComponent connect(Axis a, Component owner, Component slave)
    {
        ConnectedComponent cc = new ConnectedComponent(a, owner, slave);
        owner.addComponentListener(cc);
        return cc;
    }

}
